package com.mofum.msdom.excel.reader.impl;

import com.mofum.msdom.excel.hander.poi.AnnotationContentHandler;
import com.mofum.msdom.excel.metadata.MPSheet;
import com.mofum.msdom.excel.parser.ExcelParserCallback;
import com.mofum.msdom.excel.reader.IExcelReader;
import org.apache.commons.io.FileUtils;
import org.apache.poi.ooxml.util.SAXHelper;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.poifs.filesystem.FileMagic;
import org.apache.poi.util.TempFile;
import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.model.StylesTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author 1615690513@qq.com
 * @since 2018/11/21 0021 19:30
 */
public class AnnotationExcelReaderImpl<Data, Callback extends ExcelParserCallback<Data>> implements IExcelReader<Data, Callback> {

    public static Logger logger = LoggerFactory.getLogger(AnnotationExcelReaderImpl.class);

    public void read(File input, Class<Data> dataClass, Callback callback, Integer... sheets) {
        MPSheet[] mpSheets = new MPSheet[sheets.length];
        for (int i = 0; i < sheets.length; i++) {
            mpSheets[i] = new MPSheet();
            if (sheets[i] <= 0) {
                continue;
            }
            mpSheets[i].setIndex(sheets[i] - 1);
        }
        read(input, dataClass, callback, 0, 0, mpSheets, true);
    }

    public void read(File input, Class<Data> dataClass, Callback callback, MPSheet[] sheets) {
        read(input, dataClass, callback, 0, 0, sheets, true);
    }

    @Override
    public void read(InputStream inputStream, Class<Data> dataClass, Callback callback) {
        File tempFile = null;
        try {

            tempFile = TempFile.createTempFile("web_temp_", ".mdx");

            if (tempFile != null) {
                FileUtils.copyInputStreamToFile(inputStream, tempFile);
                read(tempFile, dataClass, callback);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
                throw new RuntimeException(e.getMessage(), e);
            }

            if (tempFile != null) {
                tempFile.delete();
            }
        }
    }

    public void read(File input, Class<Data> dataClass, Callback callback) {
        read(input, dataClass, callback, 0, 0, null, false);
    }

    public void read(File input, Class<Data> dataClass, Callback callback, int limit, int offset) {
        read(input, dataClass, callback, limit, offset, null, false);
    }

    public void read(File input, Class<Data> dataClass, Callback callback, boolean sheetForeach) {
        read(input, dataClass, callback, 0, 0, null, sheetForeach);
    }

    public void read(File input, Class<Data> dataClass, Callback callback, int limit, int offset, MPSheet[] sheets, boolean sheetForeach) {
        OPCPackage pkg = null;

        try {

            pkg = OPCPackage.open(input);

            AnnotationContentHandler annotationContentHandler = buildAnnotationContentHandler(dataClass, callback, limit, offset);

            if (sheetForeach) {

                foreachSheet(sheets, annotationContentHandler, pkg);

            } else {

                singleSheet(annotationContentHandler, pkg);

            }

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            if (pkg != null) {
                try {
                    pkg.close();
                } catch (IOException e) {
                    logger.error(e.getMessage(), e);
                    throw new RuntimeException(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * 遍历读取Sheet
     *
     * @param sheets
     * @param annotationContentHandler
     * @param pkg
     * @throws IOException
     * @throws InvalidFormatException
     * @throws ParserConfigurationException
     * @throws SAXException
     */
    private void foreachSheet(MPSheet[] sheets, AnnotationContentHandler annotationContentHandler, OPCPackage pkg) throws IOException, OpenXML4JException, ParserConfigurationException, SAXException {

        XSSFReader xssfReader = new XSSFReader(pkg);

        StylesTable styles = xssfReader.getStylesTable();

        ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(pkg);

        if (sheets == null) {

            XSSFReader.SheetIterator sheetsData = (XSSFReader.SheetIterator) xssfReader.getSheetsData();

            int sheetIndex = 0;
            while (sheetsData.hasNext()) {

                InputStream sheetInputStream = null;

                try {
                    sheetInputStream = sheetsData.next();

                    String sheetName = sheetsData.getSheetName();

                    MPSheet mpSheet = new MPSheet();

                    mpSheet.setName(sheetName);

                    mpSheet.setIndex(sheetIndex);

                    annotationContentHandler.setMpSheet(mpSheet);

                    processSheet(styles, strings, sheetInputStream, annotationContentHandler);

                    sheetIndex++;

                } finally {
                    if (sheetInputStream != null) {
                        sheetInputStream.close();
                    }
                }
            }
        } else {
            XSSFReader.SheetIterator sheetsData = (XSSFReader.SheetIterator) xssfReader.getSheetsData();

            int sheetIndex = 0;
            while (sheetsData.hasNext()) {

                MPSheet mpSheet = sheets[sheetIndex];

                if (mpSheet == null) {
                    continue;
                }
                annotationContentHandler.setLimit(mpSheet.getLimit());

                annotationContentHandler.setOffset(mpSheet.getOffset());

                mpSheet.setName(mpSheet.getName());

                annotationContentHandler.setMpSheet(mpSheet);

                processSheet(styles, strings, sheetsData.next(), annotationContentHandler);

                sheetIndex++;
            }

        }

    }

    /**
     * 单个Sheet 解析
     *
     * @param annotationContentHandler
     * @param pkg
     * @throws IOException
     * @throws InvalidFormatException
     * @throws SAXException
     * @throws ParserConfigurationException
     */
    private void singleSheet(AnnotationContentHandler annotationContentHandler, OPCPackage pkg) throws IOException, OpenXML4JException, SAXException, ParserConfigurationException {
        XSSFReader xssfReader = new XSSFReader(pkg);

        StylesTable styles = xssfReader.getStylesTable();
        ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(pkg);

        InputStream sheetInputStream = null;
        try {
            XSSFReader.SheetIterator sheetsData = (XSSFReader.SheetIterator) xssfReader.getSheetsData();

            sheetInputStream = sheetsData.next();

            MPSheet mpSheet = new MPSheet();
            mpSheet.setIndex(0);
            mpSheet.setName(mpSheet.getName());

            annotationContentHandler.setMpSheet(mpSheet);
            processSheet(styles, strings, sheetInputStream, annotationContentHandler);
        } finally {
            if (sheetInputStream != null) {
                sheetInputStream.close();
            }
        }

    }

    /**
     * 构造注解文本解析器
     *
     * @param dataClass
     * @param callback
     * @param limit
     * @param offset
     *
     */
    private AnnotationContentHandler buildAnnotationContentHandler(Class<Data> dataClass, Callback callback, int limit, int offset) {

        AnnotationContentHandler annotationContentHandler = new AnnotationContentHandler(dataClass, callback);

        annotationContentHandler.setLimit(limit);

        annotationContentHandler.setOffset(offset);

        return annotationContentHandler;
    }

    private void processSheet(StylesTable styles, ReadOnlySharedStringsTable strings, InputStream sheetInputStream, AnnotationContentHandler handler)
            throws SAXException, ParserConfigurationException, IOException {
        XMLReader sheetParser = SAXHelper.newXMLReader();

        if (handler != null) {
            sheetParser.setContentHandler(new XSSFSheetXMLHandler(styles, strings, handler, false));
        }

        sheetParser.parse(new InputSource(sheetInputStream));

    }

}
